#!/usr/bin/env python3
"""
GomerX 智能机器人测试脚本
用于测试各个模块的基本功能

该脚本使用Python的unittest框架为GomerX机器人的各个模块创建单元测试。
测试覆盖了摄像头、视觉处理、底盘、机械臂、夹爪、物品识别、导航、人脸检测、
OCR识别和巡线检测等核心模块。通过模拟(Mock)技术，可以在没有实际硬件的
情况下测试代码逻辑的正确性。

使用方法:
    python test.py

测试结果将显示在控制台中，包括每个测试用例的执行状态和总体测试结果。
"""

import os
import sys
import time
import unittest
from unittest.mock import Mock, patch

# 添加项目根目录到系统路径，确保可以导入项目模块
sys.path.append(os.path.dirname(os.path.abspath(__file__)))

# 导入需要测试的模块
from modules.camera import Camera
from modules.vision import VisionProcessor
from modules.chassis import Chassis
from modules.navigation import Navigator
from modules.object_detector import ObjectDetector
from modules.arm import Arm
from modules.gripper import Gripper
from modules.face_detector import FaceDetector
from modules.ocr_detector import OCRDetector
from config.settings import CAMERA_CONFIG, VISION_CONFIG, CHASSIS_CONFIG, ARM_CONFIG, GRIPPER_CONFIG


class TestCamera(unittest.TestCase):
    """摄像头模块测试
    
    测试Camera类的初始化、打开和关闭功能。
    使用Mock对象模拟cv2.VideoCapture，避免需要实际摄像头硬件。
    """
    
    def setUp(self):
        """测试前准备
        
        在每个测试方法执行前被调用，创建一个Camera实例用于测试。
        使用配置文件中的摄像头配置参数。
        """
        self.camera = Camera(CAMERA_CONFIG)
    
    def test_initialization(self):
        """测试初始化
        
        验证Camera对象是否正确创建，并检查分辨率参数是否正确设置。
        """
        self.assertIsNotNone(self.camera)
        self.assertEqual(self.camera.resolution, CAMERA_CONFIG["resolution"])
    
    @patch('cv2.VideoCapture')
    def test_open_close(self, mock_video_capture):
        """测试打开和关闭摄像头
        
        使用@patch装饰器模拟cv2.VideoCapture，避免需要实际摄像头硬件。
        测试摄像头的打开和关闭功能，验证状态变化和资源释放。
        
        Args:
            mock_video_capture: 模拟的cv2.VideoCapture类
        """
        # 模拟摄像头实例
        mock_instance = Mock()
        mock_instance.isOpened.return_value = True
        mock_video_capture.return_value = mock_instance
        
        # 测试打开摄像头
        result = self.camera.open()
        self.assertTrue(result)
        self.assertTrue(self.camera.is_opened)
        
        # 测试关闭摄像头
        self.camera.close()
        self.assertFalse(self.camera.is_opened)
        mock_instance.release.assert_called_once()  # 验证资源释放方法被调用


class TestVisionProcessor(unittest.TestCase):
    """视觉处理模块测试
    
    测试VisionProcessor类的图像处理功能，包括图像预处理、线条检测、
    巡线检测、颜色识别和物品识别等功能。使用numpy创建模拟图像数据进行测试。
    """
    
    def setUp(self):
        """测试前准备
        
        创建一个VisionProcessor实例用于测试。
        使用配置文件中的视觉处理配置参数。
        """
        self.vision = VisionProcessor(VISION_CONFIG)
    
    def test_initialization(self):
        """测试初始化
        
        验证VisionProcessor对象是否正确创建，并检查配置参数是否正确设置。
        """
        self.assertIsNotNone(self.vision)
        self.assertEqual(self.vision.config, VISION_CONFIG)
    
    def test_preprocess_image(self):
        """测试图像预处理
        
        创建一个模拟的彩色图像，测试预处理功能是否正常工作。
        预处理应该将彩色图像转换为灰度图像。
        """
        # 创建模拟图像 - 480x640的黑色图像
        import numpy as np
        image = np.zeros((480, 640, 3), dtype=np.uint8)
        
        # 测试预处理功能
        processed = self.vision.preprocess_image(image)
        self.assertIsNotNone(processed)
        self.assertEqual(len(processed.shape), 2)  # 预处理后应该是灰度图(2D)
    
    def test_detect_lines(self):
        """测试线条检测
        
        创建一个模拟的灰度图像，测试线条检测功能是否正常工作。
        线条检测应该返回一个线条列表，即使没有检测到线条也应返回空列表。
        """
        # 创建模拟图像 - 480x640的黑色灰度图
        import numpy as np
        image = np.zeros((480, 640), dtype=np.uint8)
        
        # 测试线条检测功能
        lines = self.vision.detect_lines(image)
        self.assertIsInstance(lines, list)  # 应该返回一个列表
    
    def test_detect_line_for_following(self):
        """测试巡线检测
        
        创建一个模拟的灰度图像，测试巡线检测功能是否正常工作。
        巡线检测应该返回线条状态和偏移量。
        """
        # 创建模拟图像 - 480x640的黑色灰度图
        import numpy as np
        image = np.zeros((480, 640), dtype=np.uint8)
        
        # 测试巡线检测功能
        line_status, offset = self.vision.detect_line_for_following(image)
        self.assertIsInstance(line_status, str)  # 应该返回字符串状态
        self.assertIsInstance(offset, (int, float))  # 应该返回数字偏移量
    
    def test_detect_color(self):
        """测试颜色检测
        
        创建一个模拟的彩色图像，测试颜色检测功能是否正常工作。
        颜色检测应该返回一个轮廓列表，即使没有检测到颜色也应返回空列表。
        """
        # 创建模拟图像 - 480x640的黑色图像
        import numpy as np
        image = np.zeros((480, 640, 3), dtype=np.uint8)
        
        # 测试颜色检测功能
        contours = self.vision.detect_color(image, "red")
        self.assertIsInstance(contours, list)  # 应该返回一个列表


class TestChassis(unittest.TestCase):
    """底盘模块测试
    
    测试Chassis类的初始化、PID控制和连接管理功能。
    使用Mock对象模拟硬件接口，避免需要实际底盘硬件。
    """
    
    def setUp(self):
        """测试前准备
        
        创建一个Chassis实例用于测试。
        使用配置文件中的底盘配置参数。
        """
        self.chassis = Chassis(CHASSIS_CONFIG)
    
    def test_initialization(self):
        """测试初始化
        
        验证Chassis对象是否正确创建，并检查速度参数是否正确设置。
        """
        self.assertIsNotNone(self.chassis)
        self.assertEqual(self.chassis.speed, CHASSIS_CONFIG["speed"])
    
    def test_pid_control(self):
        """测试PID控制
        
        测试PID控制算法的计算功能，验证对于给定的误差值，
        PID控制器能返回一个有效的控制量。
        """
        # 测试PID控制计算
        error = 10.0  # 模拟误差值
        control = self.chassis.calculate_pid_control(error)
        self.assertIsInstance(control, float)  # 控制量应该是浮点数
    
    @patch('gomerx.Chassis')
    def test_connect_disconnect(self, mock_gomerx_chassis):
        """测试连接和断开
        
        使用@patch装饰器模拟硬件接口，测试底盘的连接和断开功能。
        验证连接状态的变化和资源管理。
        
        Args:
            mock_gomerx_chassis: 模拟的底盘硬件接口类
        """
        # 模拟连接成功
        mock_instance = Mock()
        mock_gomerx_chassis.return_value = mock_instance
        
        # 测试连接
        result = self.chassis.connect()
        self.assertTrue(result)
        self.assertTrue(self.chassis.is_connected)
        
        # 测试断开
        self.chassis.disconnect()
        self.assertFalse(self.chassis.is_connected)


class TestArm(unittest.TestCase):
    """机械臂模块测试
    
    测试Arm类的初始化、位置计算和连接管理功能。
    使用Mock对象模拟硬件接口，避免需要实际机械臂硬件。
    """
    
    def setUp(self):
        """测试前准备
        
        创建一个Arm实例用于测试。
        使用配置文件中的机械臂配置参数。
        """
        self.arm = Arm(ARM_CONFIG)
    
    def test_initialization(self):
        """测试初始化
        
        验证Arm对象是否正确创建，并检查默认高度参数是否正确设置。
        """
        self.assertIsNotNone(self.arm)
        self.assertEqual(self.arm.default_height, ARM_CONFIG["default_height"])
    
    def test_calculate_pick_position(self):
        """测试计算抓取位置
        
        测试根据物体在图像中的位置计算机械臂抓取位置的功能。
        验证返回的位置是三维坐标(x, y, z)。
        """
        # 测试计算抓取位置
        object_pos = (100, 200)  # 物体在图像中的位置
        pick_pos = self.arm.calculate_pick_position(object_pos)
        self.assertIsInstance(pick_pos, tuple)
        self.assertEqual(len(pick_pos), 3)  # 应该是三维坐标(x, y, z)
    
    @patch('gomerx.Arm')
    def test_connect_disconnect(self, mock_gomerx_arm):
        """测试连接和断开
        
        使用@patch装饰器模拟硬件接口，测试机械臂的连接和断开功能。
        验证连接状态的变化和资源管理。
        
        Args:
            mock_gomerx_arm: 模拟的机械臂硬件接口类
        """
        # 模拟连接成功
        mock_instance = Mock()
        mock_gomerx_arm.return_value = mock_instance
        
        # 测试连接
        result = self.arm.connect()
        self.assertTrue(result)
        self.assertTrue(self.arm.is_connected)
        
        # 测试断开
        self.arm.disconnect()
        self.assertFalse(self.arm.is_connected)


class TestGripper(unittest.TestCase):
    """夹爪模块测试
    
    测试Gripper类的初始化和连接管理功能。
    使用Mock对象模拟硬件接口，避免需要实际夹爪硬件。
    """
    
    def setUp(self):
        """测试前准备
        
        创建一个Gripper实例用于测试。
        使用配置文件中的夹爪配置参数。
        """
        self.gripper = Gripper(GRIPPER_CONFIG)
    
    def test_initialization(self):
        """测试初始化
        
        验证Gripper对象是否正确创建，并检查打开角度参数是否正确设置。
        """
        self.assertIsNotNone(self.gripper)
        self.assertEqual(self.gripper.open_angle, GRIPPER_CONFIG["open_angle"])
    
    @patch('gomerx.Gripper')
    def test_connect_disconnect(self, mock_gomerx_gripper):
        """测试连接和断开
        
        使用@patch装饰器模拟硬件接口，测试夹爪的连接和断开功能。
        验证连接状态的变化和资源管理。
        
        Args:
            mock_gomerx_gripper: 模拟的夹爪硬件接口类
        """
        # 模拟连接成功
        mock_instance = Mock()
        mock_gomerx_gripper.return_value = mock_instance
        
        # 测试连接
        result = self.gripper.connect()
        self.assertTrue(result)
        self.assertTrue(self.gripper.is_connected)
        
        # 测试断开
        self.gripper.disconnect()
        self.assertFalse(self.gripper.is_connected)


class TestObjectDetector(unittest.TestCase):
    """物品识别模块测试
    
    测试ObjectDetector类的初始化、目标颜色设置和物品范围判断功能。
    使用Mock对象模拟摄像头和视觉处理器，避免需要实际硬件。
    """
    
    def setUp(self):
        """测试前准备
        
        创建模拟的摄像头和视觉处理器，然后创建ObjectDetector实例用于测试。
        """
        # 创建模拟摄像头和视觉处理器
        self.mock_camera = Mock()
        self.mock_vision = Mock()
        self.detector = ObjectDetector(self.mock_camera, self.mock_vision)
    
    def test_initialization(self):
        """测试初始化
        
        验证ObjectDetector对象是否正确创建，并检查摄像头和视觉处理器引用是否正确设置。
        """
        self.assertIsNotNone(self.detector)
        self.assertEqual(self.detector.camera, self.mock_camera)
        self.assertEqual(self.detector.vision, self.mock_vision)
    
    def test_set_target_color(self):
        """测试设置目标颜色
        
        测试设置目标颜色的功能，验证颜色值是否正确保存。
        """
        # 测试设置目标颜色
        self.detector.set_target_color("red")
        self.assertEqual(self.detector.target_color, "red")
    
    def test_is_object_in_range(self):
        """测试判断物品是否在范围内
        
        测试判断物品是否在机械臂可操作范围内的功能。
        使用图像中心位置作为测试用例。
        """
        # 测试判断物品是否在范围内
        object_pos = (320, 240)  # 图像中心位置
        in_range = self.detector.is_object_in_range(object_pos)
        self.assertIsInstance(in_range, bool)  # 应该返回布尔值


class TestNavigation(unittest.TestCase):
    """导航模块测试
    
    测试Navigator类的初始化和导航控制功能。
    使用Mock对象模拟视觉处理器和底盘，避免需要实际硬件。
    """
    
    def setUp(self):
        """测试前准备
        
        创建模拟的视觉处理器和底盘，然后创建Navigator实例用于测试。
        """
        # 创建模拟视觉处理器和底盘
        self.mock_vision = Mock()
        self.mock_chassis = Mock()
        self.navigator = Navigator(self.mock_vision, self.mock_chassis)
    
    def test_initialization(self):
        """测试初始化
        
        验证Navigator对象是否正确创建，并检查视觉处理器和底盘引用是否正确设置。
        """
        self.assertIsNotNone(self.navigator)
        self.assertEqual(self.navigator.vision, self.mock_vision)
        self.assertEqual(self.navigator.chassis, self.mock_chassis)
    
    def test_start_stop_navigation(self):
        """测试开始和停止导航
        
        测试导航的开始和停止功能，验证导航状态的变化。
        """
        # 测试开始导航
        self.navigator.start_navigation()
        self.assertTrue(self.navigator.is_navigating)
        
        # 测试停止导航
        self.navigator.stop_navigation()
        self.assertFalse(self.navigator.is_navigating)


class TestFaceDetector(unittest.TestCase):
    """人脸检测模块测试
    
    测试FaceDetector类的初始化、人脸检测、眼睛检测、微笑检测等功能。
    使用Mock对象模拟OpenCV的人脸检测器，避免需要实际人脸图像。
    """
    
    def setUp(self):
        """测试前准备
        
        创建一个FaceDetector实例用于测试。
        使用配置文件中的人脸检测配置参数。
        """
        self.face_detector = FaceDetector(VISION_CONFIG["face_detection"])
    
    def test_initialization(self):
        """测试初始化
        
        验证FaceDetector对象是否正确创建，并检查配置参数是否正确设置。
        """
        self.assertIsNotNone(self.face_detector)
        self.assertEqual(self.face_detector.config, VISION_CONFIG["face_detection"])
    
    @patch('cv2.CascadeClassifier.detectMultiScale')
    def test_detect_faces(self, mock_detect):
        """测试人脸检测
        
        使用@patch装饰器模拟人脸检测器，测试人脸检测功能。
        验证检测结果的格式和内容。
        
        Args:
            mock_detect: 模拟的人脸检测方法
        """
        # 模拟人脸检测结果
        mock_detect.return_value = [(100, 100, 50, 50), (200, 200, 60, 60)]
        
        # 创建模拟图像
        import numpy as np
        image = np.zeros((480, 640, 3), dtype=np.uint8)
        
        # 测试人脸检测
        faces = self.face_detector.detect_faces(image)
        self.assertIsInstance(faces, list)  # 应该返回一个列表
        self.assertEqual(len(faces), 2)     # 应该检测到两个人脸
    
    def test_get_face_features(self):
        """测试获取人脸特征
        
        测试获取人脸特征的功能，验证返回的特征字典格式。
        """
        # 创建模拟人脸矩形
        face_rect = (100, 100, 50, 50)
        
        # 测试获取人脸特征
        features = self.face_detector.get_face_features(face_rect)
        self.assertIsInstance(features, dict)  # 应该返回一个字典
        self.assertIn("face_rect", features)   # 应该包含人脸矩形
    
    def test_estimate_face_distance(self):
        """测试估算人脸距离
        
        测试根据人脸大小估算距离的功能，验证返回的距离值。
        """
        # 创建模拟人脸矩形
        face_rect = (100, 100, 50, 50)
        
        # 测试估算人脸距离
        distance = self.face_detector.estimate_face_distance(face_rect)
        self.assertIsInstance(distance, (int, float))  # 应该返回数字
        self.assertGreater(distance, 0)                # 距离应该大于0


class TestOCRDetector(unittest.TestCase):
    """OCR文字识别模块测试
    
    测试OCRDetector类的初始化、图像预处理、文本区域检测、文字识别等功能。
    使用Mock对象模拟Tesseract OCR引擎，避免需要实际安装Tesseract。
    """
    
    def setUp(self):
        """测试前准备
        
        创建一个OCRDetector实例用于测试。
        使用配置文件中的OCR检测配置参数。
        """
        self.ocr_detector = OCRDetector(VISION_CONFIG["ocr_detection"])
    
    def test_initialization(self):
        """测试初始化
        
        验证OCRDetector对象是否正确创建，并检查配置参数是否正确设置。
        """
        self.assertIsNotNone(self.ocr_detector)
        self.assertEqual(self.ocr_detector.config, VISION_CONFIG["ocr_detection"])
    
    def test_preprocess_image(self):
        """测试图像预处理
        
        创建一个模拟的彩色图像，测试图像预处理功能是否正常工作。
        预处理应该返回处理后的图像。
        """
        # 创建模拟图像 - 480x640的黑色图像
        import numpy as np
        image = np.zeros((480, 640, 3), dtype=np.uint8)
        
        # 测试图像预处理功能
        processed = self.ocr_detector.preprocess_image(image)
        self.assertIsNotNone(processed)
        self.assertEqual(processed.shape[:2], image.shape[:2])  # 尺寸应该相同
    
    @patch('cv2.findContours')
    def test_detect_text_regions(self, mock_find_contours):
        """测试文本区域检测
        
        使用@patch装饰器模拟轮廓检测，测试文本区域检测功能。
        验证检测结果的格式和内容。
        
        Args:
            mock_find_contours: 模拟的轮廓检测方法
        """
        # 模拟轮廓检测结果
        mock_find_contours.return_value = ([np.array([[100, 100], [150, 100], [150, 120], [100, 120]])], None)
        
        # 创建模拟图像
        import numpy as np
        image = np.zeros((480, 640), dtype=np.uint8)
        
        # 测试文本区域检测
        regions = self.ocr_detector.detect_text_regions(image)
        self.assertIsInstance(regions, list)  # 应该返回一个列表
    
    @patch('pytesseract.image_to_string')
    def test_recognize_text(self, mock_image_to_string):
        """测试文字识别
        
        使用@patch装饰器模拟Tesseract OCR引擎，测试文字识别功能。
        验证识别结果的格式和内容。
        
        Args:
            mock_image_to_string: 模拟的OCR识别方法
        """
        # 模拟OCR识别结果
        mock_image_to_string.return_value = "Hello World"
        
        # 创建模拟图像
        import numpy as np
        image = np.zeros((480, 640, 3), dtype=np.uint8)
        
        # 测试文字识别
        text = self.ocr_detector.recognize_text(image)
        self.assertIsInstance(text, str)     # 应该返回字符串
        self.assertEqual(text, "Hello World")  # 应该返回模拟的结果


def run_tests():
    """运行所有测试
    
    创建测试套件，添加所有测试类，并运行测试。
    最后返回测试结果，指示所有测试是否都通过。
    
    Returns:
        bool: 如果所有测试都通过返回True，否则返回False
    """
    print("GomerX 智能机器人模块测试")
    print("=" * 50)
    
    # 创建测试套件
    test_suite = unittest.TestSuite()
    
    # 添加所有测试类
    test_classes = [
        TestCamera,
        TestVisionProcessor,
        TestChassis,
        TestArm,
        TestGripper,
        TestObjectDetector,
        TestNavigation,
        TestFaceDetector,
        TestOCRDetector
    ]
    
    # 将每个测试类的所有测试方法添加到测试套件
    for test_class in test_classes:
        tests = unittest.TestLoader().loadTestsFromTestCase(test_class)
        test_suite.addTests(tests)
    
    # 运行测试套件，verbosity=2表示显示详细的测试结果
    runner = unittest.TextTestRunner(verbosity=2)
    result = runner.run(test_suite)
    
    # 返回测试结果
    return result.wasSuccessful()


if __name__ == "__main__":
    """主程序入口
    
    当直接运行此脚本时，执行所有测试并根据测试结果设置退出码。
    退出码0表示所有测试通过，非0表示有测试失败。
    """
    success = run_tests()
    sys.exit(0 if success else 1)